package genome;



import java.util.LinkedList;

import java.util.regex.Pattern;

import exceptions.FunctionExpressionException;
import exceptions.RuleFormatException;

import polylegs.Node;
import random.RandomDistrib;

import static genome.ParametersRandomGen.*;

public class Rule {
	
	//there are 3 types of rules
	// G for gradients only
	// T for type and gradient
	// S for gradient, type and subgradients
	
	// rule = 13 integers
	// first 4 : b1 = 1 if c1 always true, 0 if we check it, 4 for 4 conditions
	//condition over the color : test the type : equal or different colorModel
	// 3 * 3 for 3 conditions : variable1 relation variable2
	// 1 for action
	
	//be/ re are for teh environment
	// be = 1 if always true
	//ve = 0 for condition top is a rock, 1 for condition bottom is a rock
	private String ruletype;
	private boolean environment = false;
	
	private int b0;
	private int b1;
	private int b2;
	private int b3;
	private int be;
	
	private int ve;
	
	private int r0;
	private int v0;
	
	private int v11;
	private int r1;
	private int v12;

	private int v21;
	private int r2;
	private int v22;

	private int v31;
	private int r3;
	private int v32;

	private int a;

	// int[] rr = new int[13];

	/*
	 * public Rule (int[] rules){ rr=rules; }
	 */

	/*
	 * public Rule(int i, int j, int k, int l, int m, int n, int o) { rr[0] =
	 * i;rr[1] = j;rr[2]=k;rr[3]=l;rr[4]=m;rr[5]=n;rr[6]=o; }
	 */

	public Rule(String currentLine) {
		if (currentLine.startsWith("*")) {
			try {
				//remove the * and read the rule
				parseText(currentLine.substring(1));
			} catch (RuleFormatException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {
			parseNumbers(currentLine);

		}

	}

	public Rule(LinkedList<Constant> constants, LinkedList<Function> functions, String ruletype, boolean useEnvironment) throws RuleFormatException {
		
		this.ruletype = ruletype;
		this.environment = useEnvironment;
		//1 if always true, 0 if we check it
		b1 = Math.random()<proba_true_condition ? 1 : 0;
		b2 = Math.random()<proba_true_condition ? 1 : 0;
		b3 = Math.random()<proba_true_condition ? 1 : 0;
		

		int nb_cst = constants.size();
		int nb_functions = functions.size();
		
		if (this.ruletype.equals("T")){
			b0 = Math.random()<proba_true_type_condition ? 1 : 0;
			r0 = getRandomType();
			v0 = getRandomTypeOperator();
		}
		if(environment){
			be = Math.random()<proba_true_env_condition ? 1 : 0;
			ve = getRandomEnvCondition();
		}
		
		v11 = getRandomRuleVariable(nb_functions);
		v12 = getRandomRuleConstant(nb_cst);
		r1 = getRandomRuleOperator();
		
		v21 = getRandomRuleVariable(nb_functions);
		v22 = getRandomRuleConstant(nb_cst);
		r2 = getRandomRuleOperator();
		
		v31 = getRandomRuleVariable(nb_functions);
		v32 = getRandomRuleConstant(nb_cst);
		r3 = getRandomRuleOperator();
		
		a = getRandomRuleAction();
	}

	

	public void mutateRule(LinkedList<Constant> constants,
			LinkedList<Function> functions) throws RuleFormatException {
		// ici a priori les differents types de regles devraient faire appel a des constantes differentes
		// on verra ça plus tard
		
		// 5 mutations are possible,
		// a mutation is a replacement of a condition, the number of conditions
		// or the action
		// possibles new values are given by the properties of construction of a
		// random genome
		int nb_cst = constants.size();
		int nb_functions = functions.size();

		if (Math.random()
				* ParametersRandomGen.number_of_possible_mutations_per_rule < ParametersRandomGen.average_number_of_mutations_per_rule) {
			//1 if always true, 0 if we check it
			b1 = Math.random()<proba_true_condition ? 1 : 0;
			b2 = Math.random()<proba_true_condition ? 1 : 0;
			b3 = Math.random()<proba_true_condition ? 1 : 0;
		}
		
		if (ruletype.equals("T")||ruletype.equals("S")){
			if (Math.random()
					* ParametersRandomGen.number_of_possible_mutations_per_rule < ParametersRandomGen.average_number_of_mutations_per_rule) {
				b0 = Math.random()<proba_true_type_condition ? 1 : 0;
				r0 = getRandomType();
				v0 = getRandomTypeOperator();
			}
		}
		if(environment){
			if (Math.random()
					* ParametersRandomGen.number_of_possible_mutations_per_rule < ParametersRandomGen.average_number_of_mutations_per_rule) {
				be = Math.random()<proba_true_env_condition ? 1 : 0;
				ve = getRandomEnvCondition();
			}
		}
		if (Math.random()
				* ParametersRandomGen.number_of_possible_mutations_per_rule < ParametersRandomGen.average_number_of_mutations_per_rule) {
			v11 = getRandomRuleVariable(nb_functions);
			v12 = getRandomRuleConstant(nb_cst);
			r1 = getRandomRuleOperator();
		}
		if (Math.random()
				* ParametersRandomGen.number_of_possible_mutations_per_rule < ParametersRandomGen.average_number_of_mutations_per_rule) {
			v21 = getRandomRuleVariable(nb_functions);
			v22 = getRandomRuleConstant(nb_cst);
			r2 = getRandomRuleOperator();
		}
		if (Math.random()
				* ParametersRandomGen.number_of_possible_mutations_per_rule < ParametersRandomGen.average_number_of_mutations_per_rule) {
			v31 = getRandomRuleVariable(nb_functions);
			v32 = getRandomRuleConstant(nb_cst);
			r3 = getRandomRuleOperator();

		}
		if (Math.random()
				* ParametersRandomGen.number_of_possible_mutations_per_rule < ParametersRandomGen.average_number_of_mutations_per_rule) {
			a = getRandomRuleAction();
		}

	}

	private void parseText(String currentLine) throws RuleFormatException {
		ruletype = "G";
		if (currentLine.startsWith("G") ){
			ruletype = "G";
			currentLine = currentLine.replaceFirst("G ","");
		}
		if (currentLine.startsWith("T") ){
			ruletype = "T";
			currentLine = currentLine.replaceFirst("T ","");
		}
		if (currentLine.startsWith("S") ){
			ruletype = "S";
			currentLine = currentLine.replaceFirst("S ","");
		}
		if (currentLine.startsWith("E")){
			environment =true;
			currentLine = currentLine.replaceFirst("E ","");
		}
		
		currentLine = currentLine.replace("IF ","");
		
		Pattern p = Pattern.compile("AND |THEN ");
		Pattern p2 = Pattern.compile(" ");
		
		String[] clauses = p.split(currentLine);
		
		int index = 0;
		
		if(environment){
			String[] cond0 = p2.split(clauses[index++]);
			if (cond0[0].equals("TRUE")) {
				be = 1;
				ve = 0;
			} else {
				be=0;
				ve=OPRockFromString(cond0[0]);
			}
		}
		
		if(ruletype.equals("T")||ruletype.equals("S")){
			String[] cond0 = p2.split(clauses[index++]);
			if (cond0[0].equals("TRUE")) {
				b0 = 1;
				v0 = 0;
				r0=0;
			} else {
				b0=0;
				r0=OPTypeFromString(cond0[1]);
				v0=Integer.parseInt(cond0[2]);
			}
		}
		
		
		String[] cond1 = p2.split(clauses[index++]);
		if (cond1[0].equals("TRUE")) {
			b1 = 1;
			v11 = 0;
			v12 = 0;
			r1 = 0;
		} else {
			v11 = VARfromString(cond1[0]);
			r1 = OPfromString(cond1[1]);
			v12 = Integer.parseInt(cond1[2].replace("Constant", ""));
		}
		
		String[] cond2 = p2.split(clauses[index++]);
		if (cond2[0].equals("TRUE")) {
			b2 = 1;
			v21 = 0;
			v22 = 0;
			r2 = 0;
		} else {
			v21 = VARfromString(cond2[0]);
			r2 = OPfromString(cond2[1]);
			v22 = Integer.parseInt(cond2[2].replace("Constant", ""));
		}
		
		

		String[] cond3 = p2.split(clauses[index++]);
		if (cond3[0].equals("TRUE")) {
			b3 = 1;
			v31 = 0;
			v32 = 0;
			r3 = 0;
		} else {
			v31 = VARfromString(cond3[0]);
			r3 = OPfromString(cond3[1]);
			v32 = Integer.parseInt(cond3[2].replace("Constant", ""));
		}

		a = actionFromString(clauses[index++]);

	}

	private void parseNumbers(String currentLine) {
		ruletype = "G";
		if (currentLine.startsWith("G") ){
			ruletype = "G";
			currentLine = currentLine.replaceFirst("G ","");
		}
		if (currentLine.startsWith("T") ){
			ruletype = "T";
			currentLine = currentLine.replaceFirst("T ","");
		}
		if (currentLine.startsWith("S") ){
			ruletype = "S";
			currentLine = currentLine.replaceFirst("S ","");
		}
		if (currentLine.startsWith("E")){
			environment =true;
			currentLine = currentLine.replaceFirst("E ","");
		}
		
		Pattern p = Pattern.compile(" ");
		String[] result = p.split(currentLine);
		int index = 0;
		if(environment){
			be = Integer.parseInt(result[index++]);
		}
		if (ruletype.equals("T")||ruletype.equals("S")){
			b0 = Integer.parseInt(result[index++]);
		}
		b1 = Integer.parseInt(result[index++]);
		b2 = Integer.parseInt(result[index++]);
		b3 = Integer.parseInt(result[index++]);
		
		if(environment){
			ve = Integer.parseInt(result[index++]);
		}
		if (ruletype.equals("T")||ruletype.equals("S")){
			r0 = Integer.parseInt(result[index++]);
			v0 = Integer.parseInt(result[index++]);
		}
		
		v11 = Integer.parseInt(result[index++]);
		r1 = Integer.parseInt(result[index++]);
		v12 = Integer.parseInt(result[index++]);

		v21 = Integer.parseInt(result[index++]);
		r2 = Integer.parseInt(result[index++]);
		v22 = Integer.parseInt(result[index++]);

		v31 = Integer.parseInt(result[index++]);
		r3 = Integer.parseInt(result[index++]);
		v32 = Integer.parseInt(result[index++]);

		a = Integer.parseInt(result[index++]);
	}

	public String toString() {
		String result = "";
		if (ruletype.equals("T")){
			result = "T ";
		}
		if (ruletype.equals("S")){
			result = "S ";
		}
		if(environment){
			result += "E ";
		}
		if(environment){
			result += be+" ";
		}
		if (ruletype.equals("T")||ruletype.equals("S")){
			result += b0 + " ";
		}
		result += b1 + " ";
		result += b2 + " ";
		result += b3 + " ";
		if(environment){
			result += ve+" ";
		}
		if (ruletype.equals("T")||ruletype.equals("S")){
		result += r0 + " ";
		result += v0 + " ";
		}
		result += v11 + " ";
		result += r1 + " ";
		result += v12 + " ";
		result += v21 + " ";
		result += r2 + " ";
		result += v22 + " ";
		result += v31 + " ";
		result += r3 + " ";
		result += v32 + " ";
		result += a + " //IF ";
		if (environment){
			if(be==1){
				result += "TRUE ";
			} else {
				result += OPRockString(ve) + " ";
			}
			result += "AND ";
		}
		if (ruletype.equals("T")||ruletype.equals("S")){
			if(b0==1){
				result += "TRUE ";
			} else {
				result += OPTypeString(r0,v0) + " ";
			}
			result += "AND ";
		}
		if (b1 == 1) {
			result += "TRUE ";
		} else {
			result += OPString(r1, VARString(v11), "Constant" + v12) + " ";
		}
		result += "AND ";
		if (b2 == 1) {
			result += "TRUE ";
		} else {
			result += OPString(r2, VARString(v21), "Constant" + v22 + " ")
					+ " ";
		}
		result += "AND ";
		if (b3 == 1) {
			result += "TRUE ";
		} else {
			result += OPString(r3, VARString(v31), "Constant" + v32 + " ")
					+ " ";
		}

		result += "THEN " + actionString(a) + "\n";
		return result;

	}

	public void apply_rule(Node n, LinkedList<Integer> functions,
			LinkedList<Constant> constants) {
		try {
			int var11 = VAR(v11, n, functions);
			int var12 = CST(v12, constants);
			int var21 = VAR(v21, n, functions);
			int var22 = CST(v22, constants);
			int var31 = VAR(v31, n, functions);
			int var32 = CST(v32, constants);
			int type = n.color;
			
			boolean condition=true;
			if(environment){
				condition = condition&&((be==1)||OPRock(ve,n));
			}
			
			if (ruletype.equals("T")||ruletype.equals("S")){
				condition = condition&&((b0 == 1)
						|| OPType(type,r0, v0));
				
				
			}
			condition =  condition&&((b1 == 1)
					|| OP(r1, var11, var12))
					& ((b2 == 1) || OP(r2, var21, var22))
							& ((b3 == 1) || OP(r3, var31, var32));
			
			if (condition){		
						action(n, a);
			}	
			
		} catch (FunctionExpressionException e) {

		}

	}

	private void action(Node n, int a) {
		switch (a) {
		case 0:
			n.topP = true;
			break;
		case 1:
			n.botP = true;
			break;
		case 2:
			n.rightP = true;
			break;
		case 3:
			n.leftP = true;
			break;
		case 4:
			n.topP = false;
			break;
		case 5:
			n.botP = false;
			break;
		case 6:
			n.rightP = false;
			break;
		case 7:
			n.leftP = false;
			break;
		default :n.new_color = a-8;
			
		}
	}

	private boolean OP(int op, int var1, int var2) {
		switch (op) {
		case 0:
			return (var1 == var2);
		case 1:
			return (var1 != var2);
		case 2:
			return (var1 > var2);
		case 3:
			return (var1 < var2);
		case 4:
			return (var1 >= var2);
		case 5:
			return (var1 <= var2);
		default:
			return false;
		}
	}
		
		private boolean OPType(int my_type, int r0, int v1) {
			switch (r0) {
			case 0:
				return (my_type == v1);
			case 1:
				return (my_type != v1);
			
			default:
				return false;
			}

	}
		private boolean OPRock(int ve, Node n) {
			switch (ve) {
			case 0:
				return (n.topN.isRock());
			case 1:
				return (n.botN.isRock());
			case 2:
				return (n.rightN.isRock());
			case 3:
				return (n.leftN.isRock());
			
			default:
				return false;
			}

	}

	private int VAR(int v, Node n, LinkedList<Integer> functions)
			throws FunctionExpressionException {
		switch (v) {

		case 0:
			return n.topG;
		case 1:
			return n.botG;
		case 2:
			return n.rightG;
		case 3:
			return n.leftG;
		case 4:
			return n.topSubG;
		case 5:
			return n.botSubG;
		case 6:
			return n.rightSubG;
		case 7:
			return n.leftSubG;
		default:
			int nb_f = functions.size();
			if (functions.get((v - 8) % nb_f) != null) {
				return functions.get((v - 8) % nb_f);
			}
			throw new FunctionExpressionException(" computed ");
			// l'index des fonctions ets cyclique
		}
	}

	private int CST(int num_cst, LinkedList<Constant> constants) {
		int nb_cst = constants.size();
		return constants.get(num_cst % nb_cst).getValue();
		// on considere la liste des constantes comme un cycle
	}

	private String VARString(int v) {
		switch (v) {
		case 0:
			return "TopGradient ";
		case 1:
			return "BotGradient ";
		case 2:
			return "RightGradient ";
		case 3:
			return "LeftGradient ";
		case 4:
			return "TopSubGradient ";
		case 5:
			return "BotSubGradient ";
		case 6:
			return "RightSubGradient ";
		case 7:
			return "LeftSubGradient ";
		default:
			return "Function" + (v - 8) + " ";
		}
	}

	private int VARfromString(String var) throws RuleFormatException {
		if (var.equals("TopGradient")) {
			return 0;
		}
		if (var.equals("BotGradient")) {
			return 1;
		}
		if (var.equals("RightGradient")) {
			return 2;
		}
		if (var.equals("LeftGradient")) {
			return 3;
		}
		if (var.equals("TopSubGradient")) {
			return 4;
		}
		if (var.equals("BotSubGradient")) {
			return 5;
		}
		if (var.equals("RightSubGradient")) {
			return 6;
		}
		if (var.equals("LeftSubGradient")) {
			return 7;
		}
		if (var.contains("Function")) {
			return 8 + Integer.parseInt(var.replace("Function", ""));
		}
		throw new RuleFormatException("Variable " + var + " unknown");
	}

	private String OPString(int op, String var1, String var2) {
		switch (op) {
		case 0:
			return var1 + "= " + var2;
		case 1:
			return var1 + "!= " + var2;
		case 2:
			return var1 + "> " + var2;
		case 3:
			return var1 + "< " + var2;
		case 4:
			return var1 + ">= " + var2;
		case 5:
			return var1 + "<= " + var2;
		default:
			return " ";
		}

	}
	private String OPTypeString(int r0, int v0) {
		switch (r0) {
		case 0:
			return  "TYPE = " + v0;
		case 1:
			return  "TYPE != " + v0;
		default : 
			return " ";
		}

	}
	private String OPRockString(int ve) {
		switch (ve) {
		case 0:
			return  "Top IsARock ";
		case 1:
			return  "Bot IsARock ";
		case 2:
			return  "Right IsARock ";
		case 3:
			return  "Left IsARock ";
		default : 
			return " ";
		}

	}

	private int OPfromString(String op) throws RuleFormatException {
		if (op.equals("=")) {
			return 0;
		}
		if (op.equals("!=")) {
			return 1;
		}
		if (op.equals(">")) {
			return 2;
		}
		if (op.equals("<")) {
			return 3;
		}
		if (op.equals(">=")) {
			return 4;
		}
		if (op.equals("<=")) {
			return 5;
		}
		throw new RuleFormatException("OP " + op + " unknown");

	}
	private int OPTypeFromString(String op) throws RuleFormatException {
		if (op.equals("=")) {
			return 0;
		}
		if (op.equals("!=")) {
			return 1;
		}
		
		throw new RuleFormatException("OP " + op + " unknown");

	}
	
	private int OPRockFromString(String op) throws RuleFormatException {
		if (op.equals("Top")) {
			return 0;
		}
		if (op.equals("Bot")) {
			return 1;
		}
		if (op.equals("Right")) {
			return 2;
		}
		if (op.equals("Left")) {
			return 3;
		}
		
		throw new RuleFormatException("OP " + op + " unknown");

	}
	
	private int getRandomType(){
		//each type has teh same probability
		return (int)(Math.random()* number_of_types);
	}
	private int getRandomTypeOperator(){
		if(Math.random()<proba_rule_type){
			return 0; // return ==
		}
		return 1; // return !=
	}
	private int getRandomRuleOperator() {
		return RandomDistrib.resultat(proba_rule_operators);
	}

	
	
	private int getRandomRuleVariable(int nb_functions) throws RuleFormatException {
		//every variable has teh same probability, constants and gradient, no obstacles
		//the variable 4-7 (subgradients)are only possible if teh ruletype is S  
		if(ruletype.equals("S")){
			return (int)(Math.random() * (8+nb_functions));
		}
		if (ruletype.equals("G")||ruletype.equals("T")){
			int var = (int)(Math.random() * (4+nb_functions));
			if(var<4){
				return var;
			}
			else{
				return 4+var;
			}	
		}		
			throw new RuleFormatException("no type of rule");
	}

	private int getRandomRuleConstant(int nb_cst) {
		return (int) (Math.random() * nb_cst);
	}
	private int getRandomEnvCondition() {
		// 4 possibilites : top is a rock, bot is a rock, etc
		return (int)(Math.random()*4);
	}

	private int getRandomRuleAction() {
		//every action including change of color have teh same probability
		if (ruletype.equals("T")){
			return (int)(Math.random() * (8+number_of_types));
		}
		return (int)(Math.random() * 8);
		
		
		//return RandomDistrib.resultat(ParametersRandomGen.proba_rule_action);
	}

	private String actionString(int a) {
		switch (a) {
		case 0:
			return "OPEN TopPort";
		case 1:
			return "OPEN BotPort";
		case 2:
			return "OPEN RightPort";
		case 3:
			return "OPEN LeftPort";
		case 4:
			return "CLOSE TopPort";
		case 5:
			return "CLOSE BotPort";
		case 6:
			return "CLOSE RightPort";
		case 7:
			return "CLOSE LeftPort";
		default:return "BECOME Type"+(a-8);
			
			
		}
	}

	private int actionFromString(String action) /*throws RuleFormatException*/ {

		if (action.equals("OPEN TopPort")) {
			return 0;
		}
		if (action.equals("OPEN BotPort")) {
			return 1;
		}
		if (action.equals("OPEN RightPort")) {
			return 2;
		}
		if (action.equals("OPEN LeftPort")) {
			return 3;
		}
		if (action.equals("CLOSE TopPort")) {
			return 4;
		}
		if (action.equals("CLOSE BotPort")) {
			return 5;
		}
		if (action.equals("CLOSE RightPort")) {
			return 6;
		}
		if (action.equals("CLOSE LeftPort")) {
			return 7;
		}
		return Integer.parseInt(action.replace("BECOME Type",""))+8;
		//throw new RuleFormatException("action " + action + " unknown");

	}
}
